/*******************************************************************************
 * Copyright (C) 2010 Devin Samarin.
 * 
 * This file is part of the SUIT Toolkit.
 * 
 * The SUIT Toolkit is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * The SUIT Toolkit is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with the SUIT Toolkit.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package suit.ui;

/**
 * A Widget which holds a variable number of Widgets in a single row or column.
 * All the children are allocated the same width or height depending on the
 * orientation.
 * 
 * Generally, Widgets are added to this Widget with pack() instead of the normal
 * add() method.
 * 
 * @author Devin Samarin
 * 
 */
public class Box extends Widget {
	
	protected int focusable = FOCUS_CHILDREN;

	protected int orient;
	protected boolean homogenous;
	protected int spacing = 0;

	/**
	 * Constant for orienting the Box left to right.
	 */
	public static final int Horiz = 1;

	/**
	 * Constant for orienting the Box top to bottom.
	 */
	public static final int Vert = 2;

	/**
	 * Creates a new Box.
	 * 
	 * @param orient
	 *            orientation of the child Widgets; either Box.Horiz or Box.Vert
	 * @param homogenous
	 *            if true, all children will be given equal space allotments.
	 * @param spacing
	 *            the number of pixels to place between children
	 */
	public Box(int orient, boolean homogenous, int spacing) {
		this.homogenous = homogenous;
		this.orient = orient;
		setSpacing(spacing);
	}

	/**
	 * @return whether or not children are given equal space allotments
	 */
	public boolean isHomogenous() {
		return homogenous;
	}

	/**
	 * Set whether or not children should be given equal space allotments
	 * 
	 * @param homogenous
	 *            whether or not children are given equal space allotments
	 */
	public void setHomogenous(boolean homogenous) {
		this.homogenous = homogenous;
		invalidate(this);
	}

	/**
	 * Sets the number of pixels to place between children
	 * 
	 * @param s
	 *            the number of pixels to place between children
	 */
	public void setSpacing(int s) {
		spacing = s;
		invalidate(this);
	}

	/**
	 * Returns the number of pixels placed between children
	 * 
	 * @return the number of pixels placed between children
	 */
	public int getSpacing() {
		return spacing;
	}

	/**
	 * Returns the orientation of the child Widgets
	 * 
	 * @return the orientation of the child Widgets; either Horiz or Vert
	 */
	public int getOrient() {
		return orient;
	}

	/**
	 * Sets the orientation of the child Widgets
	 * 
	 * @param orient
	 *            the orientation of the child Widgets; either Horiz or Vert
	 */
	public void setOrient(int orient) {
		this.orient = orient;
		invalidate(this);
	}

	/**
	 * Same as calling pack( child, true, true, 0 ).
	 * 
	 * @see suit.ui.Widget#add(suit.ui.Widget)
	 */
	public void add(Widget child) {
		super.add(child);
		child.expand = true;
		child.fill = true;
		child.padding = 0;
	}

	/**
	 * Add a Widget to the end of the Box. Widget child will be ordered after
	 * any other Widgets that have already been packed with respect to the start
	 * of the Box.
	 * 
	 * @param child
	 *            the Widget to be added
	 * @param expand
	 *            Whether the new child is to be given extra space allocated to
	 *            Box. The extra space will be divided evenly between all
	 *            children of this Box that were added with expand set to true.
	 * @param fill
	 *            Whether space given to child by the expand option is actually
	 *            allocated to child. If you specify false here, then any extra
	 *            space will pad the Widget, rather than causing it to grow
	 *            larger.
	 * @param padding
	 *            extra space (in pixels) to put between this child and its
	 *            neighbors. This is over and above the global amount of padding
	 *            that was specified by the spacing parameter when the Box was
	 *            constructed. If child is the Widget at one of the start of the
	 *            Box, then padding pixels are also put between the Widget and
	 *            the leading edge.
	 */
	public void pack(Widget child, boolean expand, boolean fill, int padding) {
		super.add(child);
		child.expand = expand;
		child.fill = fill;
		child.padding = padding;
	}

	/**
	 * Change the position of a Widget in the Box.
	 * 
	 * @param child
	 *            the Widget to move
	 * @param position
	 *            the index to move the Widget to
	 */
	public void reorderChild(Widget child, int position) {
		int index = children.indexOf(child);
		children.removeElementAt(index);
		children.insertElementAt(child, position);
	}

	protected boolean onExpose(Event e) {
		Widget w;
		int size = children.size();
		int evendist = ((orient == Horiz) ? allocation.w : allocation.h)
				- borderWidth * 2;
		int evensize = size;
		if (!homogenous) {
			for (int s = 0; s < size; s++) {
				w = (Widget) children.elementAt(s);
				if (!w.expand) {
					evensize--;
					if (orient == Horiz)
						evendist -= (w.getRequisition().w - w.padding * 2);
					else
						evendist -= (w.getRequisition().h - w.padding * 2);
				}
			}
		}
		int orientmajlen;
		int orientminlen;
		int orientmajpos;
		int orientminpos;
		int curpos = borderWidth;
		for (int i = 0; i < size; i++) {
			w = (Widget) children.elementAt(i);
			if (homogenous) {
				orientmajpos = ((orient == Horiz) ? allocation.x : allocation.y)
						+ curpos + w.padding;
				orientmajlen = (int) Math.floor((evendist - spacing
						* (size - 1))
						/ evensize + 0.5)
						- w.padding * 2;
			} else {
				orientmajpos = ((orient == Horiz) ? allocation.x : allocation.y)
						+ curpos + w.padding;
				if (!w.expand)
					orientmajlen = ((orient == Horiz) ? w.getRequisition().w
							: w.getRequisition().h);
				else
					orientmajlen = (int) Math.floor((evendist - spacing
							* (size - 1))
							/ evensize + 0.5)
							- w.padding * 2;
			}
			orientminlen = ((orient == Horiz) ? allocation.h : allocation.w)
					- borderWidth * 2 - w.padding * 2;
			orientminpos = ((orient == Horiz) ? allocation.y : allocation.x)
					+ borderWidth + w.padding;
			w.sizeAllocate(new Rectangle((orient == Horiz) ? orientmajpos
					: orientminpos, (orient == Horiz) ? orientminpos
					: orientmajpos, (orient == Horiz) ? orientmajlen
					: orientminlen, (orient == Horiz) ? orientminlen
					: orientmajlen));
			curpos += orientmajlen + spacing + w.padding * 2;
			w.registerEvent(e);
		}
		return true;
	}

	Rectangle getRequisition() {
		// TODO: Respect the orientation of the box by making the major size the
		// largest requisition
		int sizeX = 1, sizeY = 1;
		int size = children.size();
		Widget w;
		Rectangle req;
		for (int i = 0; i < size; i++) {
			w = (Widget) children.elementAt(i);
			req = w.getRequisition();
			sizeX += req.w;
			sizeY += req.h;
		}
		return new Rectangle(0, 0, sizeX, sizeY);
	}
}
